library(plotly)
library(data.table)
library(tidyr)
library(knitr)
library(heatmaply)
Preprocessing
- Load data file
- rename genres for better readability
- “Religion, Spirituality & New Age” to “Religion”
- “Science.fiction” to “SciFi”
- “Action.and.Adventure” to “Action”
All genres:
books_mat <- read.csv(file_bookstore, row.names = 1)
rownames(books_mat) <- make.names(rownames(books_mat))
rownames(books_mat) <- sub("Science.fiction", "SciFi", rownames(books_mat))
rownames(books_mat) <- sub("Action.and.Adventure", "Action", rownames(books_mat))
rownames(books_mat) <- sub("Religion..Spirituality...New.Age",
"Religion", rownames(books_mat)
)
colnames(books_mat) <- rownames(books_mat)
rownames(books_mat)
[1] "Satire" "SciFi" "Drama" "Action" "Romance" "Mystery" "Horror"
[8] "Self.help" "Health" "Guide" "Travel" "Children.s" "Religion" "Science"
[15] "History" "Math" "Anthology" "Poetry" "Encyclopedias" "Dictionaries" "Comics"
[22] "Art" "Cookbooks" "Diaries" "Journals"
- Check if upper and lower triangle identical
is_upper_lower <- identical(
books_mat[upper.tri(books_mat)],
t(books_mat)[upper.tri(books_mat)]
)
is_upper_lower
[1] TRUE
- Transform to long and tidy
data.table
books_dt <- as.data.table(books_mat, keep.rownames = TRUE)
setnames(books_dt, c('genreA',colnames(books_mat)))
books_dt <- as.data.table(gather(books_dt, genreB, customers, Satire:Journals))
head(books_dt)
- Average number of genres per customer
sum(books_dt[genreA==genreB, customers])/total_customers
[1] 2.332187
First ideas
Show me everything!
hm <- heatmapr(books_mat)
heatmaply(hm,
plot_method = 'plotly',
colors = c('grey95', 'dodgerblue')
)
- Romance, SciFi, Action, History are most bought
- bought-together clusters:
- Romance, SciFi, Action, History
- Dictionaries and Comics
- Math and Poetry
- Mystery is an outlier
Most bought genre
plot_ly(data=books_dt[genreA==genreB][order(customers)],
x=~genreA, y=~customers, type="bar"
)%>% layout(
margin=list(b=100),
xaxis=list(categoryorder="trace"),
title="Most bought genre"
)
Best pairs
all_genres <- unique(books_dt$genreA)
all_pairs <- combn(all_genres, 2, simplify = F)
pair_customers <-
pair_dt <- data.table(
genre_pairs = sapply(all_pairs, function(p){
paste(sort(p), collapse = "&")}
),
pair_customers = sapply(all_pairs, function(p){
books_dt[genreA==p[1] & genreB==p[2], customers]
})
)
plot_ly(data=pair_dt[order(pair_customers, decreasing=T)][1:10], type='bar',
x=~genre_pairs, y=~pair_customers
)%>% layout(
margin=list(b=100),
xaxis=list(categoryorder="trace"),
title="Top 10 genre pairs"
)
- mostly combinations of most bought genres
Special genres
Hypothesis
- If a customer buys more than 2 genres, he is recorded in more than 1 off-diagonal entry:
- (2*diagonal - colSum) < 0
- If a genre is bought more often alone than in triplets (or higher):
- (2*diagonal - colSum) > 0
Look for customers that buy only one genre
- Compare
column sum and 2*diagonal value
- generate table with
{genre, {2*diagonal-colSum}}
all_genres <- unique(books_dt$genreA)
selective_dt <- data.table()
for(g in all_genres){
d <- books_dt[genreA==g & genreB==g, customers]
cs <- sum(books_dt[genreA==g, customers])
dd <- I(2*d - cs)
selective_dt <- rbind(selective_dt, data.table(genre=g, diag_diff=dd))
}
p_sel <- plot_ly(
data=selective_dt[order(diag_diff)],
y=~genre, x=~diag_diff, type="bar",
color = ~diag_diff>0, colors=c("gray", "darkgreen")
)%>% layout(
margin=list(l=100),
yaxis=list(categoryorder="trace", title=''),
xaxis=list(title='2*diagonal - columnSum'),
title="Which genres are bought alone?"
)
show(p_sel)
- Mystery and Horror are mostly bought alone
- Satire and Travel rather bought in pairs
Normalize columns by diagonal
books_dt[,
rel_customers:= (customers/books_dt[genreA==genreB, customers]),
by=genreB
]
head(books_dt[order(genreA)])
–> genreB relative to genreA-diagonal value
Look at all data unsorted: No pattern.
plot_ly(data=books_dt) %>%
add_heatmap(
z=~rel_customers, x=~genreA, y=~genreB, colors= c('grey95', 'dodgerblue')
) %>%
layout(
margin=list(b=110, l=110)
)
With clustering of rows and columns (Note: they are different now):
- 2 hubs on genreA axis (top dendro)
- Art, Journals, Action, SciFi, History
- Encyclopedias, Comics, Disctionaries, Poetry, Math, Anthology
- e.g. genres that were bought with Art were also bought together with Journals
- 2 hubs on genreB axis (right dendro)
- Romance, History, Action, SciFi –> Romance instead of Art and Journals
- same
- bought with everything else? Romance
Most favorite partner genre
–> about 20% customers additionally bought SciFi and Romance
Relative best pairs
–> Math is poetry and History is Science fiction!
LS0tCnRpdGxlOiAiQWxsaWFueiBEYXRhVml6IENoYWxsZW5nZSIKYXV0aG9yOiAiRGFuaWVsIEJhZGVyIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgZWNobz1UfQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGhlYXRtYXBseSkKYGBgCgoKYGBge3IsIGVjaG89RkFMU0V9Cm9wdHNfY2h1bmskc2V0KGVjaG89RkFMU0UsIGNhY2hlPUYpCnRvdGFsX2N1c3RvbWVycyA8LSAxOTUzODcKZmlsZV9ib29rc3RvcmUgPC0gZmlsZS5wYXRoKCJ+L0Rvd25sb2Fkcy90b3lkYXRhL2Jvb2tfZ2VucmVzX2RhdGEuY3N2IikKc291cmNlKCJidWlsZF9ib29rX3N0b3JlLlIiKQpgYGAKCgojIFByZXByb2Nlc3NpbmcKCiogTG9hZCBkYXRhIGZpbGUKKiByZW5hbWUgZ2VucmVzIGZvciBiZXR0ZXIgcmVhZGFiaWxpdHkKICAgICogIlJlbGlnaW9uLCBTcGlyaXR1YWxpdHkgJiBOZXcgQWdlIiB0byAiUmVsaWdpb24iCiAgICAqICJTY2llbmNlLmZpY3Rpb24iIHRvICJTY2lGaSIKICAgICogIkFjdGlvbi5hbmQuQWR2ZW50dXJlIiB0byAiQWN0aW9uIgogICAgCkFsbCBnZW5yZXM6CmBgYHtyfQpib29rc19tYXQgPC0gcmVhZC5jc3YoZmlsZV9ib29rc3RvcmUsIHJvdy5uYW1lcyA9IDEpCnJvd25hbWVzKGJvb2tzX21hdCkgPC0gbWFrZS5uYW1lcyhyb3duYW1lcyhib29rc19tYXQpKQpyb3duYW1lcyhib29rc19tYXQpIDwtIHN1YigiU2NpZW5jZS5maWN0aW9uIiwgIlNjaUZpIiwgcm93bmFtZXMoYm9va3NfbWF0KSkKcm93bmFtZXMoYm9va3NfbWF0KSA8LSBzdWIoIkFjdGlvbi5hbmQuQWR2ZW50dXJlIiwgIkFjdGlvbiIsIHJvd25hbWVzKGJvb2tzX21hdCkpCnJvd25hbWVzKGJvb2tzX21hdCkgPC0gc3ViKCJSZWxpZ2lvbi4uU3Bpcml0dWFsaXR5Li4uTmV3LkFnZSIsIAogICAgIlJlbGlnaW9uIiwgcm93bmFtZXMoYm9va3NfbWF0KQopCmNvbG5hbWVzKGJvb2tzX21hdCkgPC0gcm93bmFtZXMoYm9va3NfbWF0KQpyb3duYW1lcyhib29rc19tYXQpCmBgYAoKKiBDaGVjayBpZiB1cHBlciBhbmQgbG93ZXIgdHJpYW5nbGUgaWRlbnRpY2FsCgpgYGB7cn0KaXNfdXBwZXJfbG93ZXIgPC0gaWRlbnRpY2FsKAogICAgYm9va3NfbWF0W3VwcGVyLnRyaShib29rc19tYXQpXSwgCiAgICB0KGJvb2tzX21hdClbdXBwZXIudHJpKGJvb2tzX21hdCldCikKaXNfdXBwZXJfbG93ZXIKYGBgCgoqIFRyYW5zZm9ybSB0byBsb25nIGFuZCB0aWR5IGBkYXRhLnRhYmxlYAoKYGBge3J9CmJvb2tzX2R0IDwtIGFzLmRhdGEudGFibGUoYm9va3NfbWF0LCBrZWVwLnJvd25hbWVzID0gVFJVRSkKc2V0bmFtZXMoYm9va3NfZHQsIGMoJ2dlbnJlQScsY29sbmFtZXMoYm9va3NfbWF0KSkpCmJvb2tzX2R0IDwtIGFzLmRhdGEudGFibGUoZ2F0aGVyKGJvb2tzX2R0LCBnZW5yZUIsIGN1c3RvbWVycywgU2F0aXJlOkpvdXJuYWxzKSkKYGBgCgpgYGB7ciwgZWNobz1UfQpoZWFkKGJvb2tzX2R0KQpgYGAKCgoqIEF2ZXJhZ2UgbnVtYmVyIG9mIGdlbnJlcyBwZXIgY3VzdG9tZXIKCmBgYHtyfQpzdW0oYm9va3NfZHRbZ2VucmVBPT1nZW5yZUIsIGN1c3RvbWVyc10pL3RvdGFsX2N1c3RvbWVycwpgYGAKCgojIEZpcnN0IGlkZWFzCgojIyBTaG93IG1lIGV2ZXJ5dGhpbmchCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9OH0KaG0gPC0gaGVhdG1hcHIoYm9va3NfbWF0KQpoZWF0bWFwbHkoaG0sIAogICAgcGxvdF9tZXRob2QgPSAncGxvdGx5JywgCiAgICBjb2xvcnMgPSAgYygnZ3JleTk1JywgJ2RvZGdlcmJsdWUnKQopCmBgYAoKKiBSb21hbmNlLCBTY2lGaSwgQWN0aW9uLCBIaXN0b3J5IGFyZSBtb3N0IGJvdWdodCAKKiBib3VnaHQtdG9nZXRoZXIgY2x1c3RlcnM6CiAgICAqIFJvbWFuY2UsIFNjaUZpLCBBY3Rpb24sIEhpc3RvcnkKICAgICogRGljdGlvbmFyaWVzIGFuZCBDb21pY3MKICAgICogTWF0aCBhbmQgUG9ldHJ5CiogTXlzdGVyeSBpcyBhbiBvdXRsaWVyCgojIyBNb3N0IGJvdWdodCBnZW5yZQoKYGBge3J9CnBsb3RfbHkoZGF0YT1ib29rc19kdFtnZW5yZUE9PWdlbnJlQl1bb3JkZXIoY3VzdG9tZXJzKV0sIAogICAgeD1+Z2VucmVBLCB5PX5jdXN0b21lcnMsIHR5cGU9ImJhciIKKSU+JSBsYXlvdXQoCiAgICBtYXJnaW49bGlzdChiPTEwMCksIAogICAgeGF4aXM9bGlzdChjYXRlZ29yeW9yZGVyPSJ0cmFjZSIpLAogICAgdGl0bGU9Ik1vc3QgYm91Z2h0IGdlbnJlIgopCmBgYAoKIyMgQmVzdCBwYWlycwoKYGBge3J9CmFsbF9nZW5yZXMgPC0gdW5pcXVlKGJvb2tzX2R0JGdlbnJlQSkKYWxsX3BhaXJzIDwtIGNvbWJuKGFsbF9nZW5yZXMsIDIsIHNpbXBsaWZ5ID0gRikKcGFpcl9jdXN0b21lcnMgPC0gCnBhaXJfZHQgPC0gZGF0YS50YWJsZSgKICAgIGdlbnJlX3BhaXJzID0gc2FwcGx5KGFsbF9wYWlycywgZnVuY3Rpb24ocCl7CiAgICAgICAgcGFzdGUoc29ydChwKSwgY29sbGFwc2UgPSAiJiIpfQogICAgKSwKICAgIHBhaXJfY3VzdG9tZXJzID0gc2FwcGx5KGFsbF9wYWlycywgZnVuY3Rpb24ocCl7CiAgICAgICAgYm9va3NfZHRbZ2VucmVBPT1wWzFdICYgZ2VucmVCPT1wWzJdLCBjdXN0b21lcnNdCiAgICB9KQopCnBsb3RfbHkoZGF0YT1wYWlyX2R0W29yZGVyKHBhaXJfY3VzdG9tZXJzLCBkZWNyZWFzaW5nPVQpXVsxOjEwXSwgdHlwZT0nYmFyJywgCiAgICB4PX5nZW5yZV9wYWlycywgeT1+cGFpcl9jdXN0b21lcnMKKSU+JSBsYXlvdXQoCiAgICBtYXJnaW49bGlzdChiPTEwMCksIAogICAgeGF4aXM9bGlzdChjYXRlZ29yeW9yZGVyPSJ0cmFjZSIpLAogICAgdGl0bGU9IlRvcCAxMCBnZW5yZSBwYWlycyIKKQpgYGAKCiogbW9zdGx5IGNvbWJpbmF0aW9ucyBvZiBtb3N0IGJvdWdodCBnZW5yZXMKCgojIFNwZWNpYWwgZ2VucmVzCgpIeXBvdGhlc2lzCgoqIElmIGEgY3VzdG9tZXIgYnV5cyBtb3JlIHRoYW4gMiBnZW5yZXMsIApoZSBpcyByZWNvcmRlZCBpbiBtb3JlIHRoYW4gMSBvZmYtZGlhZ29uYWwgZW50cnk6CiAgICAqICgyKmRpYWdvbmFsIC0gY29sU3VtKSA8IDAKKiBJZiBhIGdlbnJlIGlzIGJvdWdodCBtb3JlIG9mdGVuIGFsb25lIHRoYW4gaW4gdHJpcGxldHMgKG9yIGhpZ2hlcik6IAogICAgKiAoMipkaWFnb25hbCAtIGNvbFN1bSkgPiAwCgoKTG9vayBmb3IgY3VzdG9tZXJzIHRoYXQgYnV5IG9ubHkgb25lIGdlbnJlCgoqIENvbXBhcmUgYGNvbHVtbiBzdW1gIGFuZCAgYDIqZGlhZ29uYWwgdmFsdWVgCiogZ2VuZXJhdGUgdGFibGUgd2l0aCBge2dlbnJlLCB7MipkaWFnb25hbC1jb2xTdW19fWAKCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoPTh9CmFsbF9nZW5yZXMgPC0gdW5pcXVlKGJvb2tzX2R0JGdlbnJlQSkKc2VsZWN0aXZlX2R0IDwtIGRhdGEudGFibGUoKQpmb3IoZyBpbiBhbGxfZ2VucmVzKXsKICAgIGQgPC0gYm9va3NfZHRbZ2VucmVBPT1nICYgZ2VucmVCPT1nLCBjdXN0b21lcnNdCiAgICBjcyA8LSBzdW0oYm9va3NfZHRbZ2VucmVBPT1nLCBjdXN0b21lcnNdKQogICAgZGQgPC0gSSgyKmQgLSBjcykKICAgIHNlbGVjdGl2ZV9kdCA8LSByYmluZChzZWxlY3RpdmVfZHQsIGRhdGEudGFibGUoZ2VucmU9ZywgZGlhZ19kaWZmPWRkKSkKfQoKcF9zZWwgPC0gcGxvdF9seSgKICAgIGRhdGE9c2VsZWN0aXZlX2R0W29yZGVyKGRpYWdfZGlmZildLCAKICAgIHk9fmdlbnJlLCB4PX5kaWFnX2RpZmYsIHR5cGU9ImJhciIsIAogICAgY29sb3IgPSB+ZGlhZ19kaWZmPjAsIGNvbG9ycz1jKCJncmF5IiwgImRhcmtncmVlbiIpCiklPiUgbGF5b3V0KAogICAgbWFyZ2luPWxpc3QobD0xMDApLCAKICAgIHlheGlzPWxpc3QoY2F0ZWdvcnlvcmRlcj0idHJhY2UiLCB0aXRsZT0nJyksCiAgICB4YXhpcz1saXN0KHRpdGxlPScyKmRpYWdvbmFsIC0gY29sdW1uU3VtJyksCiAgICB0aXRsZT0iV2hpY2ggZ2VucmVzIGFyZSBib3VnaHQgYWxvbmU/IgopCgpzaG93KHBfc2VsKQpgYGAKKiBNeXN0ZXJ5IGFuZCBIb3Jyb3IgYXJlIG1vc3RseSBib3VnaHQgYWxvbmUKKiBTYXRpcmUgYW5kIFRyYXZlbCByYXRoZXIgYm91Z2h0IGluIHBhaXJzCgoKCiMgTm9ybWFsaXplIGNvbHVtbnMgYnkgZGlhZ29uYWwKCmBgYHtyfQpib29rc19kdFssCiAgICByZWxfY3VzdG9tZXJzOj0gKGN1c3RvbWVycy9ib29rc19kdFtnZW5yZUE9PWdlbnJlQiwgY3VzdG9tZXJzXSksIAogICAgYnk9Z2VucmVCCiAgICBdCmhlYWQoYm9va3NfZHRbb3JkZXIoZ2VucmVBKV0pCmBgYAoKLS0+IGdlbnJlQiByZWxhdGl2ZSB0byBnZW5yZUEtZGlhZ29uYWwgdmFsdWUgCgpMb29rIGF0IGFsbCBkYXRhIHVuc29ydGVkOiBObyBwYXR0ZXJuLgoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9CnBsb3RfbHkoZGF0YT1ib29rc19kdCkgJT4lCiAgICBhZGRfaGVhdG1hcCgKICAgICAgICB6PX5yZWxfY3VzdG9tZXJzLCB4PX5nZW5yZUEsIHk9fmdlbnJlQiwgY29sb3JzPSBjKCdncmV5OTUnLCAnZG9kZ2VyYmx1ZScpCiAgICApICU+JQogICAgbGF5b3V0KAogICAgICAgIG1hcmdpbj1saXN0KGI9MTEwLCBsPTExMCkKICAgICkKYGBgCgpXaXRoIGNsdXN0ZXJpbmcgb2Ygcm93cyBhbmQgY29sdW1ucyAoTm90ZTogdGhleSBhcmUgZGlmZmVyZW50IG5vdyk6CgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9OH0KYm9va3NfcmVsbWF0IDwtIGRjYXN0KGJvb2tzX2R0LCBnZW5yZUEgfiBnZW5yZUIsIHZhbHVlLnZhciA9ICJyZWxfY3VzdG9tZXJzIikKYm9va3NfcmVsbWF0IDwtIGFzLm1hdHJpeChib29rc19yZWxtYXRbLGdlbnJlQTo9TlVMTF0pCnJvd25hbWVzKGJvb2tzX3JlbG1hdCkgPC0gY29sbmFtZXMoYm9va3NfcmVsbWF0KQoKaG1yZWwgPC0gaGVhdG1hcHIodChib29rc19yZWxtYXQpLCBrX2NvbD0zLCBrX3Jvdz0zKQpoZWF0bWFwbHkoCiAgICB4PWhtcmVsLCAKICAgIHBsb3RfbWV0aG9kID0gJ3Bsb3RseScsIAogICAgY29sb3JzID0gIGMoJ2dyZXk5NScsICdkb2RnZXJibHVlJyksCiAgICB4bGFiPSdnZW5yZUEnLCB5bGFiPSdnZW5yZUInCikgJT4lIGxheW91dCgKICAgIHRpdGxlPSdDdXN0b21lcnMgb2YgZ2VucmVCIHJlbGF0aXZlIHRvIGdlbnJlQScsCiAgICBtYXJnaW49bGlzdCh0PTUwKQopCmBgYAoKKiAyIGh1YnMgb24gZ2VucmVBIGF4aXMgKHRvcCBkZW5kcm8pCiAgICAqIEFydCwgSm91cm5hbHMsIEFjdGlvbiwgU2NpRmksIEhpc3RvcnkKICAgICogRW5jeWNsb3BlZGlhcywgQ29taWNzLCBEaXNjdGlvbmFyaWVzLCBQb2V0cnksIE1hdGgsIEFudGhvbG9neQogICAgKiBlLmcuIGdlbnJlcyB0aGF0IHdlcmUgYm91Z2h0IHdpdGggQXJ0IHdlcmUgYWxzbyBib3VnaHQgdG9nZXRoZXIgd2l0aCBKb3VybmFscwoqIDIgaHVicyBvbiBnZW5yZUIgYXhpcyAocmlnaHQgZGVuZHJvKQogICAgKiBSb21hbmNlLCBIaXN0b3J5LCBBY3Rpb24sIFNjaUZpIC0tPiBSb21hbmNlIGluc3RlYWQgb2YgQXJ0IGFuZCBKb3VybmFscwogICAgKiBzYW1lCiogYm91Z2h0IHdpdGggZXZlcnl0aGluZyBlbHNlPyBSb21hbmNlCgoKIyMgTW9zdCBmYXZvcml0ZSBwYXJ0bmVyIGdlbnJlCgpgYGB7cn0KcGxvdF9seSgKICAgIGRhdGE9Ym9va3NfZHRbLG1lZGlhbihyZWxfY3VzdG9tZXJzKSwgYnk9Z2VucmVCXVtvcmRlcihWMSwgZGVjcmVhc2luZyA9IFQpXQogICAgKSU+JQogICAgYWRkX2JhcnMoeD1+Z2VucmVCLCB5PX5WMSklPiUKICAgIGxheW91dCgKICAgICAgICB5YXhpcz1saXN0KHRpdGxlPSdNZWRpYW4gcmVsYXRpdmUgY3VzdG9tZXJzJyksCiAgICAgICAgeGF4aXM9bGlzdChjYXRlZ29yeW9yZGVyPSd0cmFjZScpLAogICAgICAgIG1hcmdpbj1saXN0KGI9MTAwKQogICAgKQpgYGAKCi0tPiBhYm91dCAyMCUgY3VzdG9tZXJzIGFkZGl0aW9uYWxseSBib3VnaHQgU2NpRmkgYW5kIFJvbWFuY2UKCgojIyBSZWxhdGl2ZSBiZXN0IHBhaXJzCgpgYGB7cn0KcGxvdF9seSgKICAgICAgICBkYXRhID0gYm9va3NfZHRbZ2VucmVBICE9IGdlbnJlQl1bb3JkZXIocmVsX2N1c3RvbWVycywgZGVjcmVhc2luZyA9IFQpXVsxOjEwXQogICAgKSAlPiUgCiAgICBhZGRfYmFycygKICAgICAgICB4PX5wYXN0ZTAoZ2VucmVBLCAiJiIsIGdlbnJlQiksIHk9fnJlbF9jdXN0b21lcnMKICAgICAgICApICU+JSAKICAgIGxheW91dCgKICAgICAgICBtYXJnaW49bGlzdChiPTEwMCwgcj04MCksIAogICAgICAgIHhheGlzPWxpc3QoY2F0ZWdvcnlvcmRlcj0idHJhY2UiLCB0aXRsZT0nJyksCiAgICAgICAgeWF4aXM9bGlzdChleHBvbmVudGZvcm1hdD0nbm9uZScpLAogICAgICAgIHRpdGxlPSJUb3AgMTAgcmVsYXRpdmUgZ2VucmUgcGFpcnMiCikKYGBgCgotLT4gKipNYXRoIGlzIHBvZXRyeSBhbmQgSGlzdG9yeSBpcyBTY2llbmNlIGZpY3Rpb24hKioK